home *** CD-ROM | disk | FTP | other *** search
/ System Booster / System Booster.iso / Archives / ARexxTools / ScionRexx.lha / GEDCOM2Scion.rexx next >
Encoding:
OS/2 REXX Batch file  |  1995-11-18  |  42.2 KB  |  1,328 lines

  1. /****************************************************************************
  2.  *                                                                          *
  3.  *                                                                          *
  4.  * $VER: GEDCOM2Scion.rexx 2.22 (17 Nov 1995)
  5.  *                                                                          *
  6.  *                      Written by Freddy Ariës                             *
  7.  * Address: Lindeboomweg 7, NL-7135 KE Harreveld, The Netherlands.          *
  8.  *                                                                          *
  9.  * This program was created to import GEDCOM data into the Scion database.  *
  10.  * It should work pretty good by now, although no guarantees whatsoever     *
  11.  * are made. If you have problems using this script, please contact me, and *
  12.  * describe exactly what the problem is, or better yet, send me a copy of   *
  13.  * the GEDCOM file you are trying to read), and I will try to fix it.       *
  14.  *                                                                          *
  15.  * GEDCOM was developed by the Family History Department of the Church of   *
  16.  * Jesus Christ of Latter-day Saints to provide a flexible uniform format   *
  17.  * for exchanging computerized genealogical data.  GEDCOM is an acronym for *
  18.  * GEnealogical Data COMmunication.  GEDCOM is provided to foster the       *
  19.  * sharing of genealogical information and the development of a wide range  *
  20.  * of inter-operable software products to assist genealogists, historians,  *
  21.  * and other researchers.                                                   *
  22.  *                                                                          *
  23.  * + SCION must be running for this AREXX script to work.                   *
  24.  * + This script uses (by default) the rexxreqtools.library (which requires *
  25.  *   a version of reqtools larger than 2.0 and rexxsyslib.library).         *
  26.  *   If you do not have these, run SetDefaults.rexx to change the settings. *
  27.  * + Even though this script does no parsing of dates, it's safer if they   *
  28.  *   are in the exact format "DD MMM YYYY".                                 *
  29.  *                                                                          *
  30.  * DONE: - progress indicator, using rexxarplib.library (requested by       *
  31.  *         Robbie J. Akins himself).                                        *
  32.  *       - now also recognizes full formal tag-names... due to the way eg.  *
  33.  *         Family Tree Maker for Windows creates its (PAF) GEDCOM output.   *
  34.  *       - should now correctly parse files containing ^M (carriage return) *
  35.  *         characters (usually MS-DOS ASCII files that weren't stripped).   *
  36.  *       - creation of external note files for multi-line GEDCOM comments   *
  37.  *         (option)                                                         *
  38.  *       - use of SOUR structure data for the Reference fields (currently   *
  39.  *         not very smart)                                                  *
  40.  *       - now uses preference file for default settings                    *
  41.  *                                                                          *
  42.  * All unrecognized fields or fields that Scion doesn't use, are skipped.   *
  43.  * NOTES:                                                                   *
  44.  * + The program generates a file FILENAME.log (where FILENAME is the       *
  45.  *  name of the GEDCOM file read), in the directory where the GEDCOM file   *
  46.  *  is located. This .log file contains parsing info about which lines were *
  47.  *  skipped and which non-fatal errors were encountered. It may be a good   *
  48.  *  idea to read this file!                                                 *
  49.  * + FAMS and FAMC fields, and EVEN structures will always be skipped,      *
  50.  *  because I use another method of establishing family (spouse & children) *
  51.  *  relationships. If no relationships are established, this probably means *
  52.  *  that the imported file does not support that other method. If you       *
  53.  *  encounter such a file, please send it to me, and tell me what program   *
  54.  *  generated it. If this happens a lot, I will add support for the parsing *
  55.  *  of these relations in a future version.                                 *
  56.  * + If you see strange strings in the Reference fields (eg. something like *
  57.  *  "R1"), you may be able to find more reference information in the GEDCOM *
  58.  *  file in the SOUR structure with that reference number (eg. @R1@).       *
  59.  *                                                                          *
  60.  * TO DO (but low priority, unless someone really wants this [?]):          *
  61.  *  - Add Shell options for the processing of note files                    *
  62.  *  - More intelligent processing of SOUR structures for Reference fields   *
  63.  *  - Add support for other character sets (like the ANSEL format that is   *
  64.  *    described in the GEDCOM specification) [external conversion program?] *
  65.  *  - More intelligent parsing of dates, and a method to handle dates with  *
  66.  *    more than 12 characters                                               *
  67.  *  - Add support for EVEN(t) structures                                    *
  68.  *  - Maybe someday even a way to allow modifying an existing database.     *
  69.  *    The current version will only add to a database, and doesn't care for *
  70.  *    double entries. Don't hold your breath for this one, though!          *
  71.  *  - Suggestions, comments, bugreports, donations, etc. are appreciated.   *
  72.  *                                                                          *
  73.  ****************************************************************************/
  74.  
  75. options failat 20; options results
  76. arg inname inval
  77.  
  78. versionstr = "2.22"
  79.  
  80. /* Don't change the settings here! Run SetDefaults.rexx instead! */
  81. usereq = 1; prgrs = 1; pgopen = 0; outp = 1
  82. scrdev = stdout
  83. PSCR = "SCIONGEN"
  84. notesdir = ""
  85.  
  86. scrname = "CON:0//639//Scion Output/AUTO/SCREEN"
  87. donotes = 0; lnum = 0
  88. NL = '0A'x
  89.  
  90. signal on IOERR
  91.  
  92. do while inname = '?'
  93.   writeln(stdout, "INFILE/A,QUIET/S,NOREQ/S ")
  94.   pull inname inval
  95. end
  96.  
  97. /* read preferences file */
  98.  
  99. if open(pfile, 'ENV:Scion/ScionRexx.prefs', 'r') then do
  100.   do while ~eof(pfile)
  101.     inln = readln(pfile)
  102.     if inln ~= "" then do
  103.       wstr = upper(word(inln, 1))
  104.       if wstr = "NOTES" then
  105.         notesdir = strip(delstr(inln, 1, length(wstr)), 'b', ' "')
  106.       else if wstr = "USEREQ" then
  107.         usereq = 1
  108.       else if wstr = "NOUSEREQ" then
  109.         usereq = 0
  110.       else if wstr = "PROGRESS" then
  111.         prgrs = 1
  112.       else if wstr = "NOPROGRESS" then
  113.         prgrs = 0
  114.       else if wstr = "PUBSCREEN" then
  115.         pscr = strip(delstr(inln, 1, length(wstr)), 'b', ' "')
  116.     end
  117.   end
  118.   close(pfile)
  119. end
  120.  
  121. if pscr = "" | (pscr ~= "WORKBENCH" & ~show('p', pscr)) then
  122.   pscr = "SCIONGEN"
  123. wstr = right(notesdir, 1)
  124. if wstr ~= '/' & wstr ~= ':' then notesdir = ""
  125. scrname = scrname||pscr
  126.  
  127. /* parse command line options, to enable calling the script automatically,
  128.  * eg. from a function key. This gets priority over global settings!
  129.  */
  130.  
  131. if inname ~= "" then do
  132.   if inname = "QUIET" | inname = "NOREQ" then do
  133.     inval = inname; inname = ""
  134.   end
  135. end
  136.  
  137. if inval = "QUIET" then do
  138.   outp = 0; usereq = 0
  139. end
  140. else if inval = "NOREQ" then usereq = 0
  141.  
  142. if usereq & ~show('l','rexxreqtools.library') then do
  143.   if exists('libs:rexxreqtools.library') then
  144.     call addlib('rexxreqtools.library',0,-30,0)
  145.   else do
  146.     usereq = 0; outp = 1
  147.     Tell("Unable to open rexxreqtools.library - using text output")
  148.   end
  149. end
  150.  
  151. if ~usereq then prgrs = 0
  152.  
  153. if prgrs & ~show('l','rexxarplib.library') then do
  154.   if exists('libs:rexxarplib.library') then
  155.     call addlib('rexxarplib.library',0,-30,0)
  156.   else
  157.     prgrs = 0
  158. end
  159.  
  160. screentofront(pscr)
  161.  
  162. /* Originally stolen from Peter Billing - thanks Peter ;-) */
  163. if ~show('P','SCIONGEN') then do
  164.   EndString('I am sorry to say that the SCION Genealogist' || NL ||,
  165.     'database is not available. Please start the' || NL ||,
  166.     'SCION program BEFORE using this script!')
  167. end
  168.  
  169. myport = "SCIONGEN"
  170. address value myport
  171. GETDBNAME
  172. dbname = upper(RESULT)
  173.  
  174. if outp & ~usereq then do
  175.   if pscr ~= "WORKBENCH" then do
  176.     scrdev = 'SCNG2SSCR'
  177.     if ~open(scrdev, scrname, 'w') then scrdev = stdout
  178.   end
  179.   Tell("GEDCOM to Scion conversion script v"||versionstr||" by Freddy Ariës")
  180.   Tell("Scion (output) database: "||dbname)
  181. end
  182.  
  183. if inname = "" then do
  184.   /* ignore the value of outp; if we can't ask for the input file,
  185.    * we can't do anything!
  186.    */
  187.   if usereq then do
  188.     /* We need a file requester for further data */
  189.     inname = rtfilerequest(,,'GEDCOM Input File',,'rt_pubscrname = '||PSCR||'   rtfi_initialpath = RAM:',)
  190.   end
  191.   else do
  192.     Tell("Please enter the filename (with complete path) of the GEDCOM file:")
  193.     TellNN("Input file: ")
  194.     inname = readln(scrdev)
  195.     inname = strip(inname, 'b', ' "')
  196.   end
  197.   if inname = '' then
  198.     EndString("ERROR: No Input File!")
  199. end
  200.  
  201. if ~open(infile, inname, "r") then
  202.   EndString("ERROR: Input file '"inname"' not found!")
  203.  
  204. if outp then do
  205.   if usereq then do
  206.     donotes = rtezrequest("Create external Note files for Scion for "||,
  207.               NL||'long GEDCOM comment lines?'||,
  208.               '',' _Yes| _No ','Input Request:','rt_pubscrname = '||pscr)
  209.     if donotes then do      
  210.       prstot='Scion Database name: '||dbname||NL
  211.       if notesdir~="" then do
  212.         prstot=prstot||'Scion Notes directory: '||NL||'"'||notesdir||'"'||NL
  213.         prstot=prstot||'The Note files will be created using this name,'||NL
  214.         prstot=prstot||'and in this directory.'||NL
  215.       end
  216.       else
  217.         prstot=prstot||'The Note files will be created using this name.'||NL
  218.       prstot=prstot||'If that is not what you want, abort and save'||NL
  219.       prstot=prstot||'your (possibly empty) database first under a'||NL
  220.       prstot=prstot||'different name! '||NL||'    Please make your choice: '
  221.       docont = rtezrequest(prstot,' _Continue | _Abort ','Converter Message:','rt_pubscrname = '||PSCR)
  222.       if docont = 0 then EXIT; /* EndString("Aborted!") */
  223.       if notesdir = "" then do
  224.         notesdir = rtfilerequest(,,'Select Notes Directory:','_Ok','rt_pubscrname = '||pscr||'   rtfi_flags = freqf_nofiles   rtfi_initialpath = RAM:',fres)
  225.         if fres = 0 then donotes = 0
  226.           /* User cancelled requester: no external note files will be created */
  227.         /* For future use:
  228.         if donotes then do
  229.           ntovw = rtezrequest('Overwrite existing note files?'||,
  230.             '',' _Yes | _No ','Converter Message:','rt_pubscrname = '||PSCR)
  231.         end
  232.      */
  233.       end
  234.     end
  235.   end
  236.   else do
  237.     Tell("Create external Note files for Scion for long")
  238.     TellNN("GEDCOM comment lines (y/n)? ")
  239.     innote = readln(scrdev)
  240.     innote = upper(left(innote, 1))
  241.     if innote = "Y" then donotes = 1
  242.     else donotes = 0
  243.     Tell("")
  244.     if donotes then do
  245.       innote = ""
  246.       do until innote = "Y" | innote = "N"
  247.         Tell("Scion Database name: "||dbname)
  248.         if notesdir ~= "" then do
  249.           Tell("Scion Notes directory: "||NL||'"'||notesdir||'"')
  250.           Tell("The Note files will be created using this name,")
  251.           Tell("and in this directory.")
  252.         end
  253.         else
  254.           Tell("The Note files will be created using this name.")
  255.         Tell("If that is not what you want, abort and save your")
  256.         Tell("(possibly empty) database first under a different name!")
  257.         TellNN("Continue (y/n): ")
  258.         innote = readln(scrdev)
  259.         innote = upper(left(innote, 1))
  260.       end
  261.       if innote ~= "Y" then EndString("Aborted.")
  262.       if notesdir = "" then do
  263.         ptmp = ""
  264.         do until ptmp = ":" | ptmp = "/"
  265.           Tell("Enter full directory name where Scion's note files are located")
  266.           TellNN("(MUST end with ':' or '/'): ")
  267.           innote = readln(scrdev)
  268.           innote = strip(innote, 'b', ' "')
  269.           ptmp = right(innote, 1)
  270.         end
  271.         notesdir = innote
  272.       end
  273.       /* For future use:
  274.       TellNN("Overwrite existing Note files? (y/n): ")
  275.       innote = readln(scrdev)
  276.       innote = upper(left(innote, 1))
  277.       if innote = "Y" then ntovw = 1
  278.       else ntovw = 0
  279.       */
  280.     end
  281.   end
  282. end
  283.  
  284. ntovw = 1
  285.  
  286. if ~usereq then
  287.   Tell("Be patient - this may take a while...")
  288.  
  289. /* Initialize line count, individual counter and family counter */
  290. ink = GetNextLine()
  291. if left(ink, 6) ~= "0 HEAD" then do
  292.   close(infile)
  293.   EndString("ERROR: Invalid beginning of file - not a valid GEDCOM format")
  294. end
  295.  
  296. lvlstr = '0'; lvl = 1; atlvl = 1
  297. IRNArr.0 = ''; IRNArr.1 = ''; FGRNArr.0 = ''; FGRNArr.1 = ''
  298.  
  299. /* Read the "HEAD(ER)" section until we find something else of level "0" */
  300.  
  301. prstot = ""
  302. ink = ParseHeader(atlvl)
  303. GETPROGVERSION
  304. prsr = RESULT
  305. prsr = "Destination: Scion Genealogist "||prsr
  306. if ~usereq then
  307.   Tell(prsr)
  308. else
  309.   prstot = prstot||prsr||NL
  310. prsr = "Dest. file:  "||dbname
  311. if ~usereq then
  312.   Tell(prsr||NL||"Scanning file for persons...")
  313. else do
  314.   prstot=prstot||prsr||NL||NL||"Parsing will take a while - be patient."||,
  315.     NL||"Click `Continue' to start parsing..."
  316.   rv = rtezrequest(prstot,'_Continue| _Abort ','Converter Message:','rt_pubscrname = '||PSCR)
  317.   if rv = 0 then EXIT
  318. end
  319.  
  320. /* TO DO: if inname ends on .GED, strip the extension */
  321. if ~open(errfile, inname||".log", "w") then
  322.   errfile = stdout
  323.  
  324. /* Now scan the following level "0" fields for individuals;
  325.  * skip the families, for the moment
  326.  */
  327.  
  328. irn = 0; famline = 0
  329.  
  330. if prgrs then do
  331.   Postmsg(10, 10, "GEDCOM to Scion (by Freddy Ariës)\Database: "||,
  332.     StripPath(inname)||"\Persons parsed: "||irn||"\ ", PSCR)
  333.   pgopen = 1
  334. end
  335.  
  336. replay = 0
  337. do while ~eof(infile)
  338.   lvlstr = word(ink, 1)
  339.   lvl = GetNumType(lvlstr)
  340.  
  341.   if lvl = atlvl then do
  342.     tagstr = upper(word(ink, words(ink)))
  343.     if (tagstr="INDI" | tagstr="INDIVIDUAL") then do
  344.       nstr = compress(word(ink, 2), '@ ')
  345.       tp = GGetIRN(nstr)
  346.       if tp ~= 0 then
  347.         writeln(errfile, "ERROR: Duplicate person encountered: "||nstr||" (IRN "||tp||") (line: "||lnum||")")
  348.       irn = irn + 1
  349.       if pgopen then Postmsg(,, "\\Persons parsed: "||irn||"\ ", PSCR)
  350.       ink = ParsePerson(nstr, lvl)
  351.       if ink ~= "" then replay = 1
  352.     end
  353.     else if ((tagstr="FAM" | tagstr="FAMILY") & famline = 0) then
  354.       famline=lnum
  355.   end
  356.   /* Skip all lines with level ~= current level (0) */
  357.   if replay = 0 then ink = GetNextLine()
  358.   else replay = 0
  359. end
  360.  
  361. if ~usereq then
  362.   Tell("Number of persons parsed: "||irn)
  363.  
  364. /* Now rescan the entire file for FAMilies; I know it is quite
  365.  * inefficient this way, but it's better to add all the persons first,
  366.  * and then establish the relations...
  367.  */
  368.  
  369. replay = 0
  370. fgrn = 0; fxs = 0
  371.  
  372. if ~usereq then
  373.   Tell("Scanning file again to establish relations...")
  374.  
  375. if pgopen then Postmsg(,, "\\\Families parsed: 0 (scanning...)", PSCR)
  376.  
  377. /* If we've already passed the first FAM line, go back to that line
  378.  * in the file. Otherwise, just continue where we are.
  379.  */
  380. if famline > 0 then do
  381.   famline = famline - 1
  382.   close(infile)
  383.   if ~open(infile, inname, 'r') then
  384.     EndString("ERROR: Unable to read relations!")
  385.   lvlstr = '0'; lvl = 1; atlvl = 1; lnum = 0
  386.   do while ~eof(infile) & lnum < famline
  387.     lnum = lnum + 1
  388.     ink = readln(infile)
  389.   end
  390. end
  391.  
  392. do while ~eof(infile)
  393.   if replay = 0 then ink = GetNextLine()
  394.   else replay = 0
  395.  
  396.   lvlstr = word(ink, 1)
  397.   lvl = GetNumType(lvlstr)
  398.  
  399.   if lvl = atlvl then do
  400.     tagstr = upper(word(ink, words(ink)))
  401.     if (tagstr = "FAM" | tagstr = "FAMILY") then do
  402.       nstr = compress(word(ink, 2),'@ ')
  403.       fp = GGetFGRN(nstr)
  404.       if fp ~= 0 then
  405.         writeln(errfile, "WARNING: Duplicate family encountered: "||nstr||" (FGRN "||fp||") (line: "||lnum||")")
  406.         /* TO DO: is the error message necessary? Or can we simply go on? */
  407.       else
  408.         fgrn = fgrn + 1
  409.       if pgopen then Postmsg(,, "\\\Families parsed: "||fgrn, PSCR)
  410.       ink = ParseFamily(nstr, lvl)
  411.       if ink ~= "" then replay = 1
  412.     end
  413.     else if (tagstr = "TRLR" | tagstr = "TRAILER") then do
  414.       close(infile)
  415.       if pgopen then do
  416.         Postmsg()
  417.         pgopen = 0
  418.       end
  419.       if usereq then do
  420.         EndString("PARSING DONE:"||NL||"Number of persons parsed: "||irn||,
  421.           NL||"Number of families parsed: "||fgrn||,
  422.       NL||NL||"DON'T FORGET TO SAVE YOUR SCION FILE!!!")
  423.       end
  424.       else do
  425.     EndString("Number of families parsed: "||fgrn||NL||,
  426.            NL||"DONE! DON'T FORGET TO SAVE YOUR SCION FILE!!!")
  427.       end
  428.     end
  429.   end
  430.   /* Skip all the fields at lvl ~= this level */
  431. end
  432. close(infile)
  433. if (ink ~= "0 TRLR") & (ink ~= "0 TRAILER") then
  434.   EndString("ERROR: Unexpected end of file")
  435. else
  436.   EndString("ERROR: Trailer not recognized! (line: "||lnum||")")
  437.  
  438. ParseHeader: PROCEDURE EXPOSE infile prstot NL outp usereq scrdev lnum pgopen pscr
  439. parse arg inilvl
  440. do while ~eof(infile)
  441.   ins = GetNextLine()
  442.   if ins = "" then
  443.     EndString("ERROR: Unexpected end of file")
  444.   lvlstr = word(ins, 1)
  445.   lvl = GetNumType(lvlstr)
  446.   if lvl <= inilvl then RETURN ins
  447.   if lvl = inilvl+1 then do
  448.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  449.     if left(curr,4) = "SOUR" then do
  450.       lstr = strip(delstr(lstr, 1, length(curr)))
  451.       prsr = "Source system: "||lstr
  452.       if ~usereq then
  453.     Tell(prsr)
  454.       else
  455.         prstot = prstot||prsr||NL
  456.       ins = ParseSource(lvl)
  457.       lvlstr = word(ins, 1)
  458.       lvl = lvlstr + 1
  459.       if lvl <= inilvl then RETURN ins
  460.       if lvl = inilvl+1 then do
  461.         lstr = strip(delstr(ins, 1, length(lvlstr)))
  462.         curr = upper(word(lstr, 1))
  463.       end
  464.       else EndString("ERROR: This should never happen [1] (line: "||lnum||")")
  465.     end
  466.     if curr = "DATE" then do
  467.       lstr = strip(delstr(lstr, 1, length(curr)))
  468.       prsr = "Creation date: "||lstr
  469.       if ~usereq then
  470.     Tell(prsr)
  471.       else
  472.         prstot = prstot||prsr||NL
  473.     end
  474.     else if curr = "FILE" then do
  475.       lstr = strip(delstr(lstr, 1, length(curr)))
  476.       prsr = "Source file:   "||lstr
  477.       if ~usereq then
  478.     Tell(prsr)
  479.       else
  480.         prstot = prstot||prsr||NL
  481.     end
  482.     /* add COPR (copyright) and GEDC VERS parsing
  483.      */
  484.   end
  485. end
  486. EndString("ERROR: Unexpected end of file")
  487.  
  488. ParseSource: PROCEDURE EXPOSE infile prstot NL outp usereq scrdev lnum pgopen pscr
  489. parse arg namlvl
  490. /* Scan for "NAME" and "VERS" */
  491. do while ~eof(infile)
  492.   ins = GetNextLine()
  493.   if ins = "" then
  494.     EndString("ERROR: Unexpected end of file")
  495.   lvlstr = word(ins, 1)
  496.   lvl = GetNumType(lvlstr)
  497.   if lvl <= namlvl then RETURN ins
  498.   if lvl = namlvl+1 then do
  499.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  500.     curr = left(upper(word(lstr, 1)),4)
  501.     if curr = "VERS" then do
  502.       lstr = strip(delstr(lstr, 1, length(curr)))
  503.       prsr = "Version:       "||lstr
  504.       if ~usereq then
  505.         Tell(prsr)
  506.       else
  507.         prstot = prstot||prsr||NL
  508.     end
  509.     else if curr = "NAME" then do
  510.       lstr = strip(delstr(lstr, 1, length(curr)))
  511.       prsr = "Created by:    "||lstr
  512.       if ~usereq then
  513.         Tell(prsr)
  514.       else
  515.         prstot = prstot||prsr||NL
  516.     end
  517.   end
  518. end
  519. EndString("ERROR: Unexpected end of file")
  520.  
  521. ParsePerson: PROCEDURE EXPOSE infile IrnArr. errfile outp usereq scrdev lnum donotes dbname ntovw notesdir pgopen pscr
  522. parse arg pnum, inilvl
  523. replay = 0
  524. prn = GetNewPerson()
  525. IRNArr.0 = IRNArr.0||pnum||' '
  526. IRNArr.1 = IRNArr.1||prn||' '
  527. noteset = 0; refset = 0; oldnotestr = ""
  528. do while ~eof(infile)
  529.   if replay = 0 then ins = GetNextLine()
  530.   else replay = 0
  531.   if ins = "" then
  532.     EndString("ERROR: Unexpected end of file")
  533.  
  534.   lvlstr = word(ins, 1)
  535.   lvl = GetNumType(lvlstr)
  536.   if lvl <= inilvl then RETURN ins
  537.   if lvl = inilvl + 1 then do
  538.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  539.     curr = upper(word(lstr, 1))
  540.     restcurr = delstr(lstr, 1, length(curr))
  541.     if curr="FAMILY_CHILD" then curr = "FAMC"
  542.     else if curr="FAMILY_SPOUSE" then curr = "FAMS"
  543.     else if curr = "REFERENCE" then curr = "REFN"
  544.     else if curr = "CHRISTENING" | curr = "ADULT_CHRISTENING" then curr = "CHR"
  545.     else if length(curr) > 4 then curr = left(curr, 4)
  546.   end
  547.  
  548.   if curr = "NAME" then StorePersName(strip(restcurr), prn)
  549.   else if curr = "SEX" then StorePersSex(strip(restcurr), prn)
  550.   else if (curr="BIRT" | curr="DEAT" | curr="BURI" | curr="CHR" | curr="BAPM" | curr="BAPL" | curr="CHRA" | curr="CONF" | curr = "BAPT") then
  551.   do
  552.     if left(curr,3) = "BAP" then curr = "BAP"
  553.     else if left(curr,3) = "CHR" then curr = "CHR"
  554.     /* note that BAPT is not official GEDCOM standard, but is for
  555.      * compatibility with long-form tags BAPTISM and BAPTISM-LDS, which are
  556.      * treated the same anyway.
  557.      */
  558.     ins = ParsePersDatePlace(curr, prn, lvl)
  559.     replay = 1
  560.   end
  561.   else if curr = "OCCU" then StoreOccup(strip(restcurr), prn)
  562.   else if curr = "EDUC" then StoreEduc(strip(restcurr), prn)
  563.   else if curr = "RELI" then StoreRelig(strip(restcurr), prn)
  564.   else if curr = "STIL" then StoreCOD("stillborn", prn)
  565.     /* Note: 'STIL' is not yet part of the official GEDCOM standard */
  566.   else if curr = "NOTE" then do
  567.     if lvl > inilvl + 1 then do
  568.       ntstr = strip(delstr(ins, 1, length(lvlstr)))
  569.       ntcurr = left(upper(word(ntstr, 1)),4)
  570.       notestr = delstr(ntstr, 1, length(ntcurr)+1)
  571.     end
  572.     else do
  573.       ntstr = lstr
  574.       ntcurr = curr
  575.       notestr = delstr(restcurr, 1, 1)
  576.     end
  577.     /* In both cases above, we only strip the first leading blank (which
  578.      * is the delimiter), and leave other leading blanks untouched.
  579.      */
  580.     if noteset = 0 then do
  581.       StorePersComment(notestr, prn)
  582.       oldnotestr = notestr
  583.       noteset = 1
  584.     end
  585.     else if donotes & (ntcurr = "NOTE" | ntcurr = "CONT" | ntcurr = "CONC") then do
  586.       nfname = notesdir||"PN"||prn||"."||dbname
  587.       if noteset = 1 then do
  588.         if ~ntovw then do
  589.       DoAppend(nfname, oldnotestr)
  590.       DoAppend(nfname, notestr)
  591.         end
  592.     else if open(notefile, nfname, 'w') then do
  593.       writeln(notefile, oldnotestr)
  594.       writeln(notefile, notestr); /* append new string */
  595.       close(notefile)
  596.     end
  597.         StorePersComment("[see notes]", prn)
  598.         noteset = 2
  599.       end
  600.       else
  601.     DoAppend(nfname, notestr); /* noteset = 2 => always append */
  602.     end
  603.     else
  604.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||ntcurr||" for person "||pnum||"! (line: "||lnum||")")
  605.   end
  606.   else if curr = "SOUR" then do
  607.     prline = strip(restcurr)
  608.     if ~refset then do
  609.       if prline ~= "" then do
  610.         prline = strip(prline,'b','@')
  611.         StorePersRefs(prline, prn)
  612.         refset = 1
  613.       end
  614.     end
  615.     else do
  616.       lostr = upper(word(strip(delstr(ins, 1, length(lvlstr))), 1))
  617.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||lostr||" for person "||pnum||"! (line: "||lnum||")")
  618.     end
  619.   end
  620.   else if (curr="FAMC" | curr="FAMS" | curr="NUMB") then do
  621.     /* nothing - children and spouse relationships are established later
  622.      * and NUMB fields are considered to be irrelevant (are not even
  623.      * part of the official GEDCOM specification, btw)
  624.      * Note: we do not output a "Skipped" message for these fields.
  625.      */
  626.   end
  627.   else if (curr = "CHAN" | curr = "REFN") then do
  628.     ins = SkipChanged(lvl)
  629.     replay = 1
  630.     /* no 'SKIPPED' message for these fields */
  631.   end
  632.   else do
  633.     olv = lvl - 1
  634.     writeln(errfile, "SKIPPED: Level "||olv||" field "||curr||" for person "||prn||"! (line: "||lnum||")")
  635.   end
  636. end
  637. EndString("ERROR: Unexpected end of file")
  638.  
  639. ParseFamily: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum fline flcnt FFile. FGRNArr. IRNArr. donotes dbname ntovw notesdir pgopen pscr
  640. parse arg fnum, inilvl
  641. replay = 0; fxs = 0; fins = 0
  642. finp = 0; flcnt = 0; fline = 0; FFile. = ""
  643. noteset = 0; refset = 0; oldnotestr = ""
  644.  
  645. /* replay: parse the currently read line, don't read the next one
  646.  * fxs   : family exists; if 0, only allow HUSB and WIFE, rest to temp-array
  647.  *       ~= 0, then contains FGRN (family number)
  648.  * finp  : file input; 0 = from sourcefile (GEDCOM), 1 = from temp-array
  649.  * fline : currently parsed line in temp-array / flcnt : max number of lines
  650.  * FFile : the temporary array used for this
  651.  */
  652.  
  653. do while (finp = 0 & ~eof(infile)) | (finp = 1 & (fline <= flcnt))
  654.   if replay = 0 then ins = GetNextFLine(finp)
  655.   else
  656.     replay = 0
  657.  
  658.   if ins = "" & finp = 0 then
  659.     EndString("ERROR: Unexpected end of file!")
  660.  
  661.   if finp = 1 & (fline > flcnt) then RETURN fins
  662.  
  663.   lvlstr = word(ins, 1)
  664.   lvl = GetNumType(lvlstr)
  665.   if (lvl <= inilvl) & (finp = 0) then do
  666.     if flcnt = 0 then RETURN ins
  667.     finp = 1; fline = 0
  668.     fins = ins; /* backup the currently read line */
  669.     ITERATE
  670.   end
  671.   if lvl = inilvl + 1 then do
  672.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  673.     curr = upper(word(lstr, 1))
  674.     restcurr = delstr(lstr, 1, length(curr))
  675.     prsid = compress(restcurr, ' @')
  676.     if curr = "DIVORCE" then curr = "DIV"
  677.     else if curr="ANNULMENT" then curr = "ANUL"
  678.     else if curr="REFERENCE" then curr = "REFN"
  679.     else if length(curr) > 4 then curr = left(curr, 4)
  680.   end
  681.  
  682.   if curr="HUSB" then fxs = StoreFamHusband(prsid, fnum)
  683.   else if curr = "WIFE" then fxs = StoreFamWife(prsid, fnum)
  684.   else if curr="CHIL" then do
  685.     if lvl > inilvl + 1 then do
  686.       olv = lvl - 1
  687.       lostr = left(upper(word(strip(delstr(ins, 1, length(lvlstr))), 1)), 4)
  688.       if lostr = "ADOP" then
  689.         StoreChildAdopt(prsid)
  690.       else
  691.         writeln(errfile, "SKIPPED: Level "||olv||" field "||lostr||" for family "||fnum||"! (line: "||lnum||")")
  692.       ITERATE
  693.     end
  694.     if fxs = 0 then do
  695.       if finp = 1 then
  696.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  697.       else
  698.         FOutput(ins)
  699.     end
  700.     else StoreFamChild(prsid, fxs)
  701.   end
  702.   else if (curr="MARR" | curr="DIV" | curr="ANUL" | curr="ENGA") then do
  703.     if fxs = 0 then do
  704.       if finp = 1 then
  705.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  706.       else
  707.         FOutput(ins)
  708.     end
  709.     ins = ParseFamDatePlace(curr, fxs, lvl, upper(prsid), finp)
  710.     if ins ~= 0 then replay = 1
  711.   end
  712.   else if curr = "NOTE" then do
  713.     if lvl > inilvl + 1 then do
  714.       ntstr = strip(delstr(ins, 1, length(lvlstr)))
  715.       ntcurr = left(upper(word(ntstr, 1)),4)
  716.       notestr = strip(delstr(ntstr, 1, length(ntcurr)))
  717.     end
  718.     else do
  719.       ntstr = lstr
  720.       ntcurr = curr
  721.       notestr = strip(restcurr)
  722.     end
  723.     if noteset = 0 then do
  724.       if fxs ~= 0 then do
  725.         StoreFamComment(notestr, fxs)
  726.         oldnotestr = notestr
  727.         noteset = 1
  728.       end
  729.       else do
  730.         if finp = 1 then
  731.           writeln(errfile, "ERROR: Family does not exist for "||ntstr||" !")
  732.         else
  733.           FOutput(ins)
  734.       end
  735.     end
  736.     else if donotes & (ntcurr = "NOTE" | ntcurr = "CONT" | ntcurr = "CONC") then do
  737.       /* only called if noteset = 1, thus fxs ~= 0 */
  738.       if fxs ~= 0 then do
  739.         nfname = notesdir||"FN"||fxs||"."||dbname
  740.         if noteset = 1 then do
  741.           if ~ntovw then do
  742.         DoAppend(nfname, oldnotestr)
  743.         DoAppend(nfname, notestr)
  744.           end
  745.       else if open(notefile, nfname, 'w') then do
  746.         writeln(notefile, oldnotestr)
  747.         writeln(notefile, notestr); /* append new string */
  748.         close(notefile)
  749.       end
  750.           StoreFamComment("[see notes]", fxs)
  751.       noteset = 2
  752.         end
  753.         else
  754.       DoAppend(nfname, notestr); /* noteset = 2 => always append */
  755.       end
  756.       else
  757.         writeln(errfile, "ERROR: Family for "||ntstr||" doesn't exist!")
  758.     end
  759.     else
  760.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||ntcurr||" for family "||fnum||"! (line: "||lnum||")")
  761.   end
  762.   else if curr = "SOUR" then do
  763.     frline = strip(restcurr)
  764.     if ~refset then do
  765.       if frline ~= "" then do
  766.     frline = strip(frline,'b','@')
  767.     if fxs ~= 0 then do
  768.       PUTFAMREFS fxs frline
  769.       refset = 1
  770.         end
  771.     else do
  772.       if finp = 1 then
  773.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  774.       else
  775.         FOutput(ins)
  776.     end
  777.       end
  778.     end
  779.     else do
  780.       lostr = upper(word(strip(delstr(ins, 1, length(lvlstr))), 1))
  781.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||lostr||" for family "||fnum||"! (line: "||lnum||")")
  782.     end
  783.   end
  784.   else if curr = "NUMB" | curr = "REFN" then do
  785.     /* No SKIPPED message for these fields, as they are irrelevant.
  786.      * NUMB fields are not even part of the official GEDCOM specification.
  787.      * It's just here because eg. ROOTS uses them a lot, and I don't want
  788.      * the program to output a "SKIPPED" line for them in the log file.
  789.      */
  790.   end
  791.   else if curr = "CHAN" then do
  792.     ins = SkipChanged(lvl)
  793.     replay = 1
  794.     /* no 'SKIPPED' message for these fields */
  795.   end
  796.   else do
  797.     olv = lvl - 1
  798.     writeln(errfile, "SKIPPED: Level "||olv||" field "||curr||" in family "||fnum||"! (line: "||lnum||")")
  799.   end
  800. end
  801. if finp = 1 then RETURN fins
  802. EndString("ERROR: Unexpected end of file!")
  803.  
  804. GetNumType: PROCEDURE EXPOSE outp infile usereq lnum pgopen pscr scrdev
  805. parse arg str
  806. if ~DATATYPE(str, 'w') then
  807.   EndString("ERROR: Level indicator expected -> error in GEDCOM specification?"||'0A'x||"String is "||str||" (line: "||lnum||")")
  808. return str + 1
  809.  
  810. GetNextFLine: PROCEDURE EXPOSE infile fline flcnt lnum FFile.
  811. parse arg finp
  812. if finp = 0 then return GetNextLine()
  813. ignl = ""
  814. do while ignl = "" & fline <= flcnt
  815.   fline = fline + 1
  816.   ignl = FFile.fline
  817.   if ignl ~= "" then ignl = strip(ignl, 'B', '     
  818. ')
  819.   /* strip leading and trailing spaces, tabs and ^M (linefeed) characters */
  820.   /* also skip empty lines */
  821. end
  822. return ignl
  823.  
  824. GetNextLine: PROCEDURE EXPOSE infile lnum
  825. lnum = lnum + 1
  826. ignl = ""
  827. do while ignl = "" & ~eof(infile)
  828.   ignl = readln(infile)
  829.   if ignl ~= "" then ignl = strip(ignl, 'B', '     
  830. ')
  831.   /* strip leading and trailing spaces, tabs and ^M (linefeed) characters */
  832.   /* also skip empty lines */
  833. end
  834. return ignl
  835.  
  836. FOutput: PROCEDURE EXPOSE flcnt FFile.
  837. parse arg iline
  838. FFile.flcnt = iline
  839. flcnt = flcnt + 1
  840. return 0
  841.  
  842. StorePersName: PROCEDURE
  843. parse arg nstr, pnum
  844. nstr = strip(nstr, 'B', '/')
  845. ps = pos('/', nstr)
  846. if ps = 0 then do
  847.   fname = ""
  848.   lname = nstr
  849. end
  850. else do
  851.   fname = left(nstr, ps-1)
  852.   lname = compress(right(nstr, length(nstr)-ps),'/')
  853. end
  854. PUTLASTNAME pnum lname
  855. PUTFIRSTNAME pnum fname
  856. return 1
  857.  
  858. StorePersSex: PROCEDURE
  859. parse arg nstr, pnum
  860.  sxstr = upper(left(nstr, 1))
  861.  if sxstr ~= 'M' then sxstr = 'F'
  862. PUTSEX pnum sxstr
  863. return 1
  864.  
  865. ParsePersDatePlace: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum pgopen pscr
  866. parse arg idstr, pnum, inilvl
  867. datstr = ""
  868. plcstr = ""
  869. causestr = ""
  870. do while ~eof(infile)
  871.   ins = GetNextLine()
  872.   if eof(infile) then
  873.     EndString("ERROR: Unexpected end of file!")
  874.   lvlstr = word(ins, 1)
  875.   lvl = GetNumType(lvlstr)
  876.   if lvl <= inilvl then do
  877.     select
  878.       when idstr = "BIRT" then do
  879.     if datstr ~= "" then
  880.       PUTBIRTHDATE pnum datstr
  881.     if plcstr ~= "" then
  882.       PUTBIRTHPLACE pnum plcstr
  883.       end
  884.       when idstr = "DEAT" then do
  885.     if datstr ~= "" then
  886.       PUTDEATHDATE pnum datstr
  887.     if plcstr ~= "" then
  888.       PUTDEATHPLACE pnum plcstr
  889.     if causestr ~= "" then
  890.       PUTDIEDOF pnum causestr
  891.       end
  892.       when idstr = "BURI" then do
  893.     if datstr ~= "" then
  894.       PUTBURIALDATE pnum datstr
  895.     if plcstr ~= "" then
  896.       PUTBURIALPLACE pnum plcstr
  897.       end
  898.       when (idstr="BAP" | idstr="CHR" | idstr="CONF") then do
  899.     if datstr ~= "" then
  900.       PUTBAPTISMDATE pnum datstr
  901.     if plcstr ~= "" then
  902.       PUTBAPTISMPLACE pnum plcstr
  903.       end
  904.       otherwise
  905.         /* do nothing */
  906.     end
  907.     RETURN ins
  908.   end
  909.   else if lvl = inilvl+1 then do
  910.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  911.     curr = upper(word(lstr, 1))
  912.     if curr = "DATE" then do
  913.       datstr = strip(delstr(lstr, 1, length(curr)))
  914.       /* TO DO: add some more parsing of the date string
  915.        * - dates ending on /x (indicating a choice of years)
  916.        * - "BET" dates (between 2 dates)
  917.        * etc.
  918.        */
  919.     end
  920.     else if (curr="PLAC" | curr="PLACE") then do
  921.       plcstr = strip(delstr(lstr, 1, length(curr)))
  922.     end
  923.     else if (curr="QUAY" | curr="QUALITY_OF_DATA") then do
  924.       /* only add '?' for QUAY 0 fields */
  925.       lstr = strip(delstr(lstr, 1, length(curr)))
  926.       if DATATYPE(lstr, 'w') & lstr < 1 then do
  927.         if datstr ~= "" then datstr = datstr||'?'
  928.         if plcstr ~= "" then plcstr = plcstr||'?'
  929.       end
  930.     end
  931.     else if (curr="CAUS" | curr="CAUSE") then do
  932.       causestr = strip(delstr(lstr, 1, length(curr)))
  933.     end
  934.   end
  935.   else do
  936.     /* lvl > inilvl+1 */
  937.     qlstr = strip(delstr(ins, 1, length(lvlstr)))
  938.     qcurr = upper(word(qlstr, 1))
  939.     if (qcurr="QUAY" | qcurr="QUALITY_OF_DATA") then do
  940.       /* only add '?' for QUAY 0 fields */
  941.       qlstr = strip(delstr(qlstr, 1, length(qcurr)))
  942.       if DATATYPE(qlstr, 'w') & qlstr < 1 then do
  943.         if curr = "DATE" & datstr ~= "" then
  944.       datstr = datstr||'?'
  945.         if (curr = "PLAC" | curr = "PLACE") & plcstr ~= "" then
  946.       plcstr = plcstr||'?'
  947.       end
  948.     end
  949.     else do
  950.       /* else: skip all other fields of level inilvl+1 */
  951.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||qcurr||" for person "||pnum||"! (line: "||lnum||")")
  952.     end
  953.   end
  954. end
  955. return 0
  956.  
  957. ParseFamDatePlace: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum fline flcnt pgopen pscr FFile. FGRNArr.
  958. parse arg idstr, ff, inilvl, idr, finp
  959. datstr = ""; plcstr = ""; clbrnt = ""; wtness = ""
  960. if idstr="ANUL" then divtype = 4
  961. else if (idr = "N" & idstr="DIV") then divtype = 1
  962. else divtype = 2
  963. /* some programs (like PAF 2.2) have "DIV Y" and "DIV N" fields
  964.  * DIV Y (yes) is treated identical to "DIV" (without arguments)
  965.  * DIV N (no)  is treated as 'Ending: None'
  966.  */
  967. do while ~eof(infile) | (fline < flcnt)
  968.   ins = GetNextFLine(finp)
  969.  
  970.   if finp = 0 & ins = "" then
  971.     EndString("ERROR: Unexpected end of file (Parsing Family Events)!")
  972.  
  973.   if finp = 1 & (fline > flcnt) then do
  974.     if ff ~= 0 then do
  975.       if idstr="MARR" then do
  976.         if datstr ~= "" then
  977.         PUTMARRYDATE ff datstr
  978.         if plcstr ~= "" then
  979.         PUTMARRYPLACE ff plcstr
  980.         if clbrnt ~= "" then
  981.           PUTCELEBRANT ff clbrnt
  982.         if wtness ~= "" then
  983.           PUTWITNESS ff wtness
  984.       end
  985.       else if (idstr="ANUL" | idstr="DIV") then do
  986.         if datstr ~= "" then
  987.         PUTENDDATE ff datstr
  988.         if plcstr ~= "" then
  989.         PUTENDPLACE ff plcstr
  990.     PUTENDING ff divtype
  991.       end
  992.       else if idstr="ENGA" then do
  993.         if datstr ~= "" then
  994.         PUTENGAGEDATE ff datstr
  995.         if plcstr ~= "" then
  996.         PUTENGAGEPLACE ff plcstr
  997.       end
  998.     end
  999.     RETURN 0
  1000.   end
  1001.  
  1002.   lvlstr = word(ins, 1)
  1003.   lvl = GetNumType(lvlstr)
  1004.   if lvl <= inilvl then do
  1005.     if ff ~= 0 then do
  1006.       if idstr="MARR" then do
  1007.         if datstr ~= "" then
  1008.       PUTMARRYDATE ff datstr
  1009.         if plcstr ~= "" then
  1010.       PUTMARRYPLACE ff plcstr
  1011.         if clbrnt ~= "" then
  1012.           PUTCELEBRANT ff clbrnt
  1013.         if wtness ~= "" then
  1014.           PUTWITNESS ff wtness
  1015.       end
  1016.       else if (idstr="DIV" | idstr="ANUL") then do
  1017.         if datstr ~= "" then
  1018.         PUTENDDATE ff datstr
  1019.         if plcstr ~= "" then
  1020.         PUTENDPLACE ff plcstr
  1021.         PUTENDING ff divtype
  1022.       end
  1023.       else if idstr="ENGA" then do
  1024.         if datstr ~= "" then
  1025.         PUTENGAGEDATE ff datstr
  1026.         if plcstr ~= "" then
  1027.         PUTENGAGEPLACE ff plcstr
  1028.       end
  1029.     end
  1030.     RETURN ins
  1031.   end
  1032.   if finp = 0 & ff = 0 then FOutput(ins)
  1033.   else do
  1034.     if lvl = inilvl+1 then do
  1035.       lstr = strip(delstr(ins, 1, length(lvlstr)))
  1036.       curr = upper(word(lstr, 1))
  1037.       if curr="QUALITY_OF_DATA" then curr = "QUAY"
  1038.       else if length(curr) > 4 then curr = left(curr, 4)
  1039.       if curr = "DATE" then do
  1040.         datstr = strip(delstr(lstr, 1, length(curr)))
  1041.         /* TO DO: add some more parsing of the date string */
  1042.       end
  1043.       else if curr="PLAC" then do
  1044.         plcstr = strip(delstr(lstr, 1, length(curr)))
  1045.       end
  1046.       else if curr="OFFI" then do
  1047.         clbrnt = strip(delstr(lstr, 1, length(curr)))
  1048.         /* only for "MARR" */
  1049.       end
  1050.       else if curr="WITN" then do
  1051.         wtness = strip(delstr(lstr, 1, length(curr)))
  1052.         /* only for "MARR" */
  1053.       end
  1054.       else if curr="QUAY" then do
  1055.         /* only add '?' for QUAY 0 fields */
  1056.         lstr = strip(delstr(lstr, 1, length(curr)))
  1057.         if DATATYPE(lstr, 'w') & lstr < 1 then do
  1058.           if datstr ~= "" then datstr = datstr||'?'
  1059.           if plcstr ~= "" then plcstr = plcstr||'?'
  1060.         end
  1061.       end
  1062.       else if (curr = "TYPE" & idstr = "DIV") then do
  1063.         lstr = upper(strip(delstr(lstr, 1, length(curr))))
  1064.         if left(lstr, 3) = "SEP" then divtype = 3
  1065.         else if left(lstr, 4) = "DEAT" then divtype = 5
  1066.         else divtype = 2
  1067.         /* default is 'DIVORCE' */
  1068.       end
  1069.     end
  1070.     else if lvl > inilvl + 1 then do
  1071.       qlstr = strip(delstr(ins, 1, length(lvlstr)))
  1072.       qcurr = upper(word(qlstr, 1))
  1073.       if (qcurr="QUAY" | qcurr="QUALITY_OF_DATA") then do
  1074.         /* only add '?' for QUAY 0 fields */
  1075.         qlstr = strip(delstr(qlstr, 1, length(qcurr)))
  1076.         if DATATYPE(qlstr, 'w') & qlstr < 1 then do
  1077.           if curr = "DATE" & datstr ~= "" then
  1078.         datstr = datstr||'?'
  1079.           if curr = "PLAC" & plcstr ~= "" then
  1080.         plcstr = plcstr||'?'
  1081.         end
  1082.       end
  1083.       else do
  1084.         /* else: skip all other fields of level inilvl+1 */
  1085.         writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||qcurr||" for family "||ff||"! (line: "||lnum||")")
  1086.       end
  1087.     end
  1088.   end
  1089. end
  1090. EndString("ERROR: Unexpected end of file (Parsed Family Events)!")
  1091.  
  1092. GetNewPerson: PROCEDURE EXPOSE infile outp usereq scrdev pgopen pscr
  1093.   PUTNEWPERSON
  1094.   newpnum = RESULT
  1095.   if newpnum = 0 then EndString("ERROR: Cannot allocate new person!")
  1096.   /* if you want to see Scion in action, uncomment the next line */
  1097.   /* GETPERSONWIN newpnum */
  1098. return newpnum
  1099.  
  1100. GetNewFamily: PROCEDURE EXPOSE infile outp usereq scrdev pgopen pscr
  1101. parse arg irn
  1102.   PUTNEWFAMILY irn
  1103.   newfnum = RESULT
  1104.   if newfnum = 0 then EndString("ERROR: Cannot allocate new family!")
  1105.   /* if you want to see Scion in action, uncomment the next line */
  1106.   /* GETFAMILYWIN newfnum */
  1107. return newfnum
  1108.  
  1109. StoreOccup: PROCEDURE
  1110. parse arg nstr, pnum
  1111.  PUTOCCUPATION pnum nstr
  1112. return 1
  1113.  
  1114. StoreEduc: PROCEDURE
  1115. parse arg nstr, pnum
  1116.  PUTEDUCATION pnum nstr
  1117. return 1
  1118.  
  1119. StoreRelig: PROCEDURE
  1120. parse arg nstr, pnum
  1121.  PUTRELIGION pnum nstr
  1122. return 1
  1123.  
  1124. StoreCOD: PROCEDURE
  1125. parse arg nstr, pnum
  1126.  PUTDIEDOF pnum nstr
  1127. return 1
  1128.  
  1129. StorePersComment: PROCEDURE
  1130. parse arg nstr, pnum
  1131.   if pnum ~= 0 then
  1132.     PUTPERSCOMMENT pnum nstr
  1133. return 1
  1134.  
  1135. StorePersRefs: PROCEDURE
  1136. parse arg nstr, pnum
  1137.   if pnum ~= 0 then
  1138.     PUTPERSREFS pnum nstr
  1139. return 1
  1140.  
  1141. StoreFamHusband: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1142. parse arg nstr, fnum
  1143.   nstr = compress(nstr,'@ ')
  1144.   ff = 0
  1145.   ii = GGetIRN(nstr)
  1146.   if ii = 0 then
  1147.     writeln(errfile, "ERROR: Missing Personal Record for HUSBAND "||nstr||" (line: "||lnum||")")
  1148.   else do
  1149.     ff = GGetFGRN(fnum)
  1150.     if ff = 0 then do
  1151.     ff = GetNewFamily(ii)
  1152.     FGRNArr.0 = FGRNArr.0||fnum||' '
  1153.     FGRNArr.1 = FGRNArr.1||ff||' '
  1154.     end
  1155.     else do
  1156.     /* There already is a family, so there is a principal; assume
  1157.      * that that is the wife - add the husband as spouse
  1158.      */
  1159.       PUTSPOUSE ff ii
  1160.     ers = RESULT
  1161.     if ers ~= 1 then do
  1162.         writeln(errfile, "ERROR "||ers||" in PUTSPOUSE (HUSB) "||ff||' '||ii)
  1163.       GETPRINCIPAL ff
  1164.       prc = RESULT
  1165.       GETSPOUSE ff
  1166.       spc = RESULT
  1167.       writeln(errfile, "Principal: "||prc||", Spouse: "||spc)
  1168.     end
  1169.     end
  1170.   end
  1171. return ff
  1172.  
  1173. StoreFamWife: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1174. parse arg nstr, fnum
  1175.   nstr = compress(nstr,'@ ')
  1176.   ff = 0
  1177.   ii = GGetIRN(nstr)
  1178.   if ii = 0 then
  1179.     writeln(errfile, "ERROR: Missing Personal Record for WIFE "||nstr||" (line: "||lnum||")")
  1180.   else do
  1181.     ff = GGetFGRN(fnum)
  1182.     if ff = 0 then do
  1183.       ff = GetNewFamily(ii)
  1184.     FGRNArr.0 = FGRNArr.0||fnum||' '
  1185.     FGRNArr.1 = FGRNArr.1||ff||' '
  1186.     end
  1187.     else do
  1188.       PUTSPOUSE ff ii
  1189.     ers = RESULT
  1190.     if ers ~= 1 then do
  1191.         writeln(errfile, "ERROR "||ers||" in PUTSPOUSE (WIFE) "||ff||' '||ii)
  1192.       GETPRINCIPAL ff
  1193.       prc = RESULT
  1194.       GETSPOUSE ff
  1195.       spc = RESULT
  1196.       writeln(errfile, "Principal: "||prc||", Spouse: "||spc)
  1197.     end
  1198.     end
  1199.   end
  1200. return ff
  1201.  
  1202. StoreFamChild: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1203. parse arg nstr, fnum
  1204. /* TO DO: improve this function, to allow definition of children here,
  1205.  *      instead of in a separate personal record. Also look for "ADOP"
  1206.  *      field (adopted children)
  1207.  */
  1208.   if fnum = 0 then RETURN 0
  1209.     /* we cannot parse a child when there is no family yet */
  1210.   nstr = compress(nstr,'@ ')
  1211.   ii = GGetIRN(nstr)
  1212.   if ii = 0 then
  1213.     writeln(errfile, "ERROR: Missing Personal Record for CHILD "||nstr||" (line: "||lnum||")")
  1214.   else do
  1215.     PUTCHILD fnum ii
  1216.     ers = RESULT
  1217.     if ers ~= 1 then
  1218.       writeln(errfile, "ERROR "||ers||" in PUTCHILD "||fnum||' '||ii||" (line: "||lnum||")")
  1219.   end
  1220. return 1
  1221.  
  1222. StoreChildAdopt: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr.
  1223. parse arg nstr
  1224.   /* This uses an as yet undocumented (and maybe even unsupported) feature
  1225.    * in Scion v4.07 and above.  So don't be surprised if you see italicized
  1226.    * names in the Family Details window (try alt-clicking on a child to
  1227.    * toggle this)
  1228.    */
  1229.   nstr = compress(nstr,'@ ')
  1230.   ii = GGetIRN(nstr)
  1231.   if ii = 0 then
  1232.     writeln(errfile, "ERROR: Missing Personal Record for CHILD "||nstr||" (line: "||lnum||")")
  1233.   else
  1234.     PUTADOPTION ii 1
  1235. return 1
  1236.  
  1237. StoreFamComment: PROCEDURE
  1238. parse arg nstr, ff
  1239.   PUTFAMCOMMENT ff nstr
  1240. return 1
  1241.  
  1242. /* Return the Scion IRN belonging to the GEDCOM Personal number pnum */
  1243. GGetIRN: PROCEDURE EXPOSE IRNArr.
  1244. parse arg pnum
  1245. anum = find(IRNArr.0, pnum)
  1246. if anum > 0 then
  1247.   return word(IRNArr.1, anum)
  1248. else
  1249.   return 0
  1250.  
  1251. /* Return the Scion FGRN belonging to the GEDCOM Family number fnum */
  1252. GGetFGRN: PROCEDURE EXPOSE lnum FGRNArr.
  1253. parse arg fnum
  1254. anum = find(FGRNArr.0, fnum)
  1255. if anum > 0 then
  1256.   return word(FGRNArr.1, anum)
  1257. else
  1258.   return 0
  1259.  
  1260. DoAppend: PROCEDURE 
  1261. parse arg fname, ostr
  1262. if exists(fname) then
  1263.   rval = open(notefile, fname, 'a')
  1264. else
  1265.   rval = open(notefile, fname, 'w')
  1266. if rval then do
  1267.   writeln(notefile, ostr)
  1268.   close(notefile)
  1269. end
  1270. return 0
  1271.  
  1272. SkipChanged: PROCEDURE EXPOSE infile lnum
  1273. parse arg inlvl
  1274. lvl = inlvl + 1
  1275. do until lvl <= inlvl
  1276.   ins = GetNextLine()
  1277.   lvlstr = word(ins, 1)
  1278.   lvl = GetNumType(lvlstr)
  1279. end
  1280. return ins
  1281.  
  1282. /*
  1283.  * Procedure to strip the directory path from the string,
  1284.  * only leaving the filename
  1285.  */
  1286. StripPath: PROCEDURE
  1287. parse arg str
  1288.   p = lastpos('/', str)
  1289.   if p > 0 then ret1 = delstr(str,1,p)
  1290.   else ret1 = str
  1291.   p = lastpos(':', ret1)
  1292.   if p > 0 then retstr = delstr(ret1,1,p)
  1293.   else retstr = ret1
  1294. return retstr
  1295.  
  1296. Tell: PROCEDURE EXPOSE outp scrdev
  1297. parse arg str
  1298. if outp then writeln(scrdev, str)
  1299. return 0
  1300.  
  1301. TellNN: PROCEDURE EXPOSE outp scrdev
  1302. parse arg str
  1303. if outp then writech(scrdev, str)
  1304. return 0
  1305.  
  1306. EndString: PROCEDURE EXPOSE usereq outp pgopen pscr scrdev infile
  1307. parse arg str
  1308. if pgopen then Postmsg()
  1309. /* If you turned off stdout, no error messages will be shown! */
  1310. if usereq then
  1311.   rtezrequest(str,'E_xit','Converter Message:','rt_pubscrname = '||PSCR)
  1312. else
  1313.   Tell(str || '0A'x)
  1314. if outp & ~usereq & (scrdev ~= stdout) then do
  1315.   Tell("Press <return> to exit.")
  1316.   readln(scrdev)
  1317.   close(scrdev)
  1318. end
  1319. close(infile)
  1320. EXIT
  1321.  
  1322. /* Let's make sure you get a nice message when you turn off the printer :-) */
  1323.  
  1324. IOERR:
  1325.   bline = SIGL
  1326.   say "I/O error #"||RC||" detected in line "||bline||":"
  1327.   say sourceline(bline)
  1328.   if pgopen then Postmsg()
  1329.   EXIT
  1330.